
/*
  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at 
  http://www.mozilla.org/MPL/ 
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License. 
  
  The Original Code is core2: ECMAScript core objects 2nd gig. 
  
  The Initial Developer of the Original Code is
  Zwetan Kjukov <zwetan@gmail.com>.
  Portions created by the Initial Developer are Copyright (C) 2003-2007
  the Initial Developer. All Rights Reserved.
  
  Contributor(s):
*/

if( !_global.buRRRn.Reflection )
    {
    /* Singleton: Reflection
       Provide utility function for reflection mecanism in ECMA-262.
       
    */
    _global.buRRRn.Reflection = {};
    }

/* property: memoize
   Allows to save objects path in 
   a special __path__ property
*/
buRRRn.Reflection.memoize = false;

/* Method: getTypeOf
   Returns the type of the passed object.
*/
buRRRn.Reflection.getTypeOf = function( o )/*String*/
    {
    if( o === undefined )
        {
        return "undefined";
        }
    
    if( o === null )
        {
        return "null";
        }
    
    switch( o.constructor )
        {
        case Array:    return "array";
        case Boolean:  return "boolean";
        case Date:     return "date";
        case Error:    return "error";
        case Function: return "function";
        case Number:   return "number";
        case String:   return "string";
        case Object:   return "object";
        default:       return( typeof o );
        }
    }


/* Method: getCoreObjectPath
*/
buRRRn.Reflection.getCoreObjectPath = function( o )/*String*/
    {
    switch( o )
        {
        case Array:    return "Array";
        case Boolean:  return "Boolean";
        case Date:     return "Date";
        case Error:    return "Error";
        case Function: return "Function";
        case Number:   return "Number";
        case String:   return "String";
        case Object:   return "Object";
        }
    
    return undefined;
    }

/* Method: getConstructorPath
   Returns the constructor path of the instance as string.
*/
buRRRn.Reflection.getConstructorPath = function( o )/*String*/
    {
    if( o == _global )
        {
        return "Object";
        }
    /*
    switch( o.constructor )
        {
        case Array:    return "Array";
        case Boolean:  return "Boolean";
        case Date:     return "Date";
        case Error:    return "Error";
        case Function: return "Function";
        case Number:   return "Number";
        case String:   return "String";
        case Object:   return "Object";
        }
    */
    var path = this.getObjectPath( o.constructor );
    
    if( path == undefined )
        {
        path = "Object"; //by default any ECMAScript objects inherit from object Object
        }
    
    return path;
    }

/* Method: getConstructorName
   Returns the constructor name of the current instance as string.
*/
buRRRn.Reflection.getConstructorName = function( o )/*String*/
    {
    var path = this.getConstructorPath( o );
    
    if( path.indexOf( "." ) > -1 )
        {
        path = path.split( "." );
        return path.pop();
        }
    
    return path;
    }

/* Method: 
   Returns the constructor reference from a string constructor name.
*/
buRRRn.Reflection.getConstructorByName = function( name/*String*/ )/*Function*/
    {
    var path;
    var original = name;
    
    if( name.indexOf(".") > -1 )
        {
        path = name.split( "." );
        }
    else
        {
        path = [ name ];
        }
    
    var root = path.shift();
    var ref = _global[ root ];
    
    if( ref == undefined )
        {
        throw new Error( "_global."+original+" does not exists" );
        }
    
    
    for( var i=0; i<path.length; i++ )
        {
        ref = ref[ path[i] ];
        
        if( ref == undefined )
            {
            throw new Error( "_global."+original+" does not exists" );
            }
        }
    
    if( typeof ref == "function" )
        {
        return ref;
        }
    else
        {
        throw new Error( "_global."+original+" is not a function constructor" );
        }
    }

buRRRn.Reflection.getConstructorMethods = function( o, inherited/*Boolean*/ )/*Array*/
    {
    if( inherited == null )
        {
        inherited = false;
        }
    
    var member;
    var members = [];
    
    for( member in o.prototype )
        {
        if( typeof o.prototype[member] == "function" )
            {
            if( inherited || (o === Object) )
                {
                members.push( member );
                }
            else if( o.prototype[member] != Object.prototype[member] )
                {
                members.push( member );
                }
            }
        }
    
    return members;
    }

buRRRn.Reflection.getObjectMethods = function( o )/*Array*/
    {
    var member;
    var members = [];
    
    for( member in o )
        {
        if( typeof o[member] == "function" )
            {
            if( (o !== Object) &&
                (o[member] !== Object.prototype[member]) &&
                (o[member] !== Object[member]) )
                {
                members.push( member );
                }
            else if( (o === Object) &&
                     (o[member] !== Object.prototype[member]) &&
                     (o[member] !== Function.prototype[member]) )
                {
                members.push( member );
                }
            }
        }
    
    return members;
    }

buRRRn.Reflection.getMethodByName = function( o, name/*String*/ )/*Function*/
    {
    var methods  = this.getConstructorMethods( o );
    var omethods = this.getObjectMethods( o );
    
    if( methods.indexOf( name ) > -1 )
        {
        return o.prototype[name];
        }
    
    if( omethods.indexOf( name ) > -1 )
        {
        return o[name];
        }
    
    return null;
    }

/* Method: hasConstructorByName
   Returns a boolean telling if the constructor exists from a string name.
*/
buRRRn.Reflection.hasConstructorByName = function( name/*String*/ )/*Boolean*/
    {
    try
        {
        var ctor = this.getConstructorByName( name );
        }
    catch( e )
        {
        return false;
        }
    
    return true;
    }

/* Method: getObjectPath
   Returns the string path of the target object.
   
   Parameters:
     target - un object target path 
     scope  - optional, allow you to scan members in an object scope
   
   note:
   You can only retrieve path of enumerable objects defined in _global,
   objects marked as DontEnum are not resolved,
   internal and build-in objects are in general marked as DontEnum.
   
   You will not be able to resolve path of local variable in function scope.
*/
buRRRn.Reflection.getObjectPath = function( target/*Object*/, scope )/*String*/
    {
    var maxRecursion, commands, values, refs, name, ref, scopePath, isCoreObject, m, mm;
    maxRecursion = 200; //better if < 0xFF
    commands     = [];
    values       = [];
    refs         = [];
    scopePath    = "";
    
    if( target == null )
        {
        return undefined;
        }
    
    if( target === _global )
        {
        return "_global";
        }
    
    isCoreObject = this.getCoreObjectPath( target );
    if( isCoreObject != undefined )
        {
        return isCoreObject;
        }
    
    if( scope == null )
        {
        scope = _global; //by default the scope to scan is global
        }
    else
        {
        scopePath = this.getCoreObjectPath( scope );
        
        if( scopePath == undefined )
            {
            scopePath = this.getObjectPath( scope );
            }
        
        }
    
    //path already found
    if( target.__path__ )
        {
        return target.__path__;
        }
    
    /* internalFunction: list
    */
    var list = function()
        {
        name = values.pop();
        ref  = refs.pop();
        
        if (ref == null)
            {
            values.push( "" );
            }
        else
            {
            
            for( m in ref )
                {
                /* attention:
                   if the host does not mark "prototype"
                   as DontEnum (as it should be!)
                   this can cause recursions errors,
                   so we want to avoid that.
                */
                if( m == "prototype" )
                    {
                    continue;
                    }
                
                /* attention:
                   we do not want to itterate trough Array indexes,
                   especially with Mozilla JavaScript as
                   <http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:String>
                   indicate that string can be accessed as array
                   ex: 'cat'[1] // returns "a"
                */
                if( (ref[m] != null) && (ref[m].constructor == Array) )
                    {
                    continue;
                    }
                
                /* note:
                   We want to scan only objects and functions,
                   the only type being able to contain child members.
                */
                if( ((typeof(ref[m]) == "object") || (typeof(ref[m]) == "function")) &&
                    (ref.hasOwnProperty( m )) )
                    {
                    commands.push( list );
                    values.push( name + "." + m );
                    refs.push( ref[m] );
                    }
                
                }
            }
        }
    
    for( mm in scope )
        {
        /* attention:
           We do not want to scan global reserved objects,
           especially for browsers where it cause recursion errors.
        */
        if( buRRRn.core2.isGlobalReserved( mm ) )
            {
            continue;
            }
        
        commands.push( list );
        values.push( mm );
        refs.push( scope[mm] );
        }
    
    if( scope == _global )
        {
        var i, coreObjects, coreObject;
        coreObjects = ["Array","Boolean","Date","Error","Function","Number","String","Object"]
        
        for( i=0; i<coreObjects.length; i++ )
            {
            coreObject = coreObjects[i];
            
            commands.push( list );
            values.push( coreObject );
            refs.push( scope[coreObject] );
            }
        }
    
    while( (commands.length != 0) && (commands.length < maxRecursion ) )
        {
        commands.pop()();
        
        if( ref === target ) //we want to test identity!!!
            {
            /* note:
               Here we save the result of the path
               to not recompute it later.
               see: <http://www.nist.gov/dads/HTML/memoize.html>
            */
            if( this.memoize && (scope == _global) )
                {
                target.__path__ = name;
                }
            
            return scopePath+name;
            }
        }
    
    return undefined;
    }




